package cn.thoughtworks.school.programCenter.services;

import cn.thoughtworks.school.programCenter.entities.Assignment;
import cn.thoughtworks.school.programCenter.entities.ReviewQuiz;
import cn.thoughtworks.school.programCenter.entities.User;
import cn.thoughtworks.school.programCenter.entities.commands.CreateQuizSuggestion;
import cn.thoughtworks.school.programCenter.entities.dto.AssignmentQuizData;
import cn.thoughtworks.school.programCenter.exceptions.BusinessException;
import cn.thoughtworks.school.programCenter.feign.QuizCenterFeign;
import cn.thoughtworks.school.programCenter.repositories.ReviewQuizRepository;
import cn.thoughtworks.school.programCenter.util.CloneRepertory;
import cn.thoughtworks.school.programCenter.util.FileCompressUtil;
import cn.thoughtworks.school.programCenter.util.FileDownloadUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.FileUtils;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.io.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class QuizCenterService {
    @Value("${quizCenter}")
    private String quizCenter;
    @Autowired
    private UserCenterService userCenterService;

    @Autowired
    private QuizCenterFeign quizCenterFeign;
    @Autowired
    private AssignmentService assignmentService;
    @Autowired
    private ReviewQuizRepository reviewQuizRepository;
    private ObjectMapper oMapper = new ObjectMapper();

    public List getSelectingBasicQuizzes(String ids) {
        return quizCenterFeign.getBasicQuizzes(ids).getBody();
    }

    public List getSelectHomeworkQuizzes(String ids) {
        return quizCenterFeign.getHomeworkQuizzesByIds(ids, "many").getBody();
    }

    public List getSelectOnlineCodingQuizzes(String ids) {
        return quizCenterFeign.getOnlineCodingQuizzesByIds(ids, "many").getBody();
    }

    public List getSelectOnlineLanguageQuizzes(String ids) {
        return quizCenterFeign.getOnlineLanguageQuizzesByIds(ids, "many").getBody();
    }

    public List getSelectHomeworkWithUserAnswer(String ids, Long assignmentId, Long studentId) {
        return quizCenterFeign.getHomeworkWithUserAnswer(studentId, assignmentId, ids).getBody();
    }

    public List getSelectOnlineCodingWithUserAnswer(String ids, Long assignmentId, Long studentId) {
        return quizCenterFeign.getHomeworkWithUserAnswer(studentId, assignmentId, ids).getBody();
    }

    public List getSelectOnlineLanguageWithUserAnswer(String ids, Long assignmentId, Long studentId) {
        return quizCenterFeign.getOnlineLanguageQuizzesWithUserAnswer(studentId, assignmentId, ids).getBody();
    }

    public List getSelectSubjectiveQuizzes(String ids) {
        return quizCenterFeign.getSubjectiveQuizzes(ids).getBody();
    }

    public List getSelectLogicQuizzes(String ids) {
        return quizCenterFeign.getLogicQuizzes(ids).getBody();
    }

    public ResponseEntity<List> getTags(Long userId) {
        return quizCenterFeign.getSearchTags(userId);
    }

    public List getSelectSubjectiveQuizzesWithUserAnswer(String ids, Long assignmentId, Long studentId) {
        return quizCenterFeign.getSubjectiveQuizzesWithUserAnswer(studentId, assignmentId, ids).getBody();
    }

    public List getSelectSimpleCodingQuizzesWithUserAnswer(String ids, Long assignmentId, Long studentId) {
        return quizCenterFeign.getSimpleCodingQuizzesWithUserAnswer(studentId, assignmentId, ids).getBody();
    }

    public ResponseEntity<Map> submitSubjectiveAnswer(Long assignmentId, Long quizId, Long studentId, Map data) throws BusinessException {
        assignmentService.finishAssignment(assignmentId, studentId);
        return quizCenterFeign.submitSubjectiveAnswer(studentId, assignmentId, quizId, data);
    }


    public ResponseEntity<Map> submitHomeworkQuizAnswer(Map data) throws BusinessException {
        assignmentService.finishAssignment(Long.parseLong(data.get("assignmentId").toString()),
            Long.parseLong(data.get("studentId").toString()));
        return quizCenterFeign.createSubmission(data);
    }

    public ResponseEntity<Map> submitOnlineCodingQuizAnswer(Map data) throws BusinessException {
        assignmentService.finishAssignment(Long.parseLong(data.get("assignmentId").toString()),
            Long.parseLong(data.get("studentId").toString()));
        return quizCenterFeign.judgeOnlineCodingQuiz(data);
    }

    /**
     * TODO: today
     */
    public ResponseEntity<Map> submitOnlineLanguageQuizAnswer(Map data) throws BusinessException {
        assignmentService.finishAssignment(Long.parseLong(data.get("assignmentId").toString()),
            Long.parseLong(data.get("studentId").toString()));
        return quizCenterFeign.addSubmission(data);
    }

    public Map getHomeworkQuizzesLogs(Long submissionId, Long assignmentId, Long quizId, Long userId) {
        if (submissionId == 0) {
            return quizCenterFeign.getSubmissionHomeworkLogBySubmissionId(assignmentId, quizId, userId).getBody();
        }
        return quizCenterFeign.getSubmissionHomeworkLogBySubmissionId(submissionId).getBody();
    }

    public Map getOnlineCodingQuizzesLogs(Long submissionId, Long assignmentId, Long quizId, Long userId) {
        if (submissionId == 0) {
            return quizCenterFeign.getSubmissionHomeworkLogBySubmissionId(userId, assignmentId, quizId).getBody();
        }
        return quizCenterFeign.getSubmissionHomeworkLogBySubmissionId(submissionId).getBody();
    }

    public Map getOnlineLanguageQuizzesLogs(Long submissionId, Long assignmentId, Long quizId, Long userId) {
        if (submissionId == 0) {
            return quizCenterFeign.getSubmissionOnlineLanguageLogBySubmissionId(userId, assignmentId, quizId).getBody();
        }
        return quizCenterFeign.getSubmissionOnlineLanguageLogBySubmissionId(submissionId).getBody();
    }

    @Transactional
    public List submitBasicQuizAnswer(Long assignmentId, List<Map> answer, Long studentId) throws BusinessException {
        assignmentService.finishAssignment(assignmentId, studentId);
        return quizCenterFeign.submitBasicQuiz(studentId, assignmentId, answer).getBody();
    }

    @Transactional
    public List submitSurveyQuizAnswer(Long assignmentId, List<Map> answer, Long studentId) throws BusinessException {
        assignmentService.finishAssignment(assignmentId, studentId);
        return quizCenterFeign.submitSurveyQuizAnswer(studentId, assignmentId, answer).getBody();
    }

    public List getSelectingBasicQuizzesWithUserAnswer(String ids, Long assignmentId, Long studentId) {
        return quizCenterFeign.getBasicQuizList(studentId, assignmentId, ids).getBody();
    }

    public Map getHomeworkQuizById(Long quizId) {
        return quizCenterFeign.getCoding(quizId.toString(), "").getBody();
    }

    public Map getOnlineCodingQuizById(Long quizId) {
        return quizCenterFeign.getOnlineCoding(quizId.toString(), "").getBody();
    }

    public Map getOnlineLanguageQuizById(Long quizId) {
        return quizCenterFeign.getOnlineLanguageCoding(quizId.toString(), "").getBody();
    }

    public ResponseEntity<String> getOnlineCodingAnswerFile(Long quizId) {
        return quizCenterFeign.getOnlineCodingQuizzes(quizId);
    }

    public ResponseEntity<String> getOnlineLanguageAnswerFile(Long quizId) {
        return quizCenterFeign.getOnlineLanguageQuizzes(quizId);
    }

    public List getQuizzesAndAnswerByQuizIds(String ids, Long assignmentId, Long studentId, String type) {
        List quizzes = new ArrayList();
        if (ids.equals("")) {
            return quizzes;
        }
        if (type.equals("BASIC_QUIZ")) {
            quizzes = this.getSelectingBasicQuizzesWithUserAnswer(ids, assignmentId, studentId);
        } else if ("HOMEWORK_QUIZ".equals(type)) {
            quizzes = this.getSelectHomeworkWithUserAnswer(ids, assignmentId, studentId);
        } else if ("ONLINE_CODING_QUIZ".equals(type)) {
            quizzes = this.getSelectOnlineCodingWithUserAnswer(ids, assignmentId, studentId);
        } else if ("ONLINE_LANGUAGE_QUIZ".equals(type)) {
            quizzes = this.getSelectOnlineLanguageWithUserAnswer(ids, assignmentId, studentId);
        } else if ("SUBJECTIVE_QUIZ".equals(type)) {
            quizzes = this.getSelectSubjectiveQuizzesWithUserAnswer(ids, assignmentId, studentId);
        } else if ("SIMPLE_CODING_QUIZ".equals(type)) {
            quizzes = this.getSelectSimpleCodingQuizzesWithUserAnswer(ids, assignmentId, studentId);
        } else if ("SURVEY_QUIZ".equals(type)) {
            quizzes = this.getSelectSurveyQuizzesWithUserAnswer(ids, assignmentId, studentId);
        }
        return quizzes;
    }

    private List getSelectSurveyQuizzesWithUserAnswer(String ids, Long assignmentId, Long studentId) {
        return quizCenterFeign.getSurveyQuizzesSubmission(studentId, assignmentId, ids).getBody();
    }

    public Map getSubmissionByAssignmentIdAndQuizId(Long userId, Long assignmentId, Long quizId) {
        return quizCenterFeign.getSubmissionByAssignmentIdAndQuizId(userId, assignmentId, quizId).getBody();
    }

    public ResponseEntity createSimpleCodingSubmission(Map data) throws BusinessException {
        assignmentService.finishAssignment(Long.parseLong(data.get("assignmentId").toString()),
            Long.parseLong(data.get("studentId").toString()));
        return quizCenterFeign.createSimpleCodingSubmission(data);
    }

    public InputStream getStudentsAnswerFile(Long assignmentId, Long quizId, String userIds) throws IOException {
        List<Map> users = userCenterService.getUsersByIds(userIds);
        String randomPath = UUID.randomUUID().toString();
        List<Map<String, String>> answers = getUsersRepo(assignmentId, quizId, users);
        getUsersAnswer(randomPath, answers);
        packageRepo(randomPath);
        InputStream inputStream = convertRepoToInputStream(randomPath);
        deleteTempSpace(randomPath);

        return inputStream;
    }

    private List<Map<String, String>> getUsersRepo(Long assignmentId, Long quizId, List<Map> users) {
        List<Map<String, String>> result = new ArrayList<>();
        users.forEach(user -> {
            Map<String, String> answer = new HashMap<>();
            Long userId = Long.parseLong(user.get("id").toString());
            Map submission = getSubmissionByAssignmentIdAndQuizId(userId, assignmentId, quizId);
            if (Objects.nonNull(submission.get("id"))) {
                answer.put("name", String.valueOf(user.get("name")));
                answer.put("repo", String.valueOf(submission.get("userAnswer")));
                answer.put("zipUrl", String.valueOf(submission.get("userAnswerZipUrl")));
                answer.put("branch", String.valueOf(submission.get("answerBranch")));
                result.add(answer);
            }
        });

        return result;
    }

    private InputStream convertRepoToInputStream(String randomPath) throws FileNotFoundException {
        String tempSpacePath = getTempSpacePath(randomPath);
        File answer = new File(tempSpacePath + "/answer.zip");
        return new FileInputStream(answer);
    }

    private void deleteTempSpace(String randomPath) throws IOException {
        String tempSpacePath = getTempSpacePath(randomPath);
        FileUtils.deleteDirectory(new File(tempSpacePath));
    }

    private void packageRepo(String randomPath) throws IOException {
        String tempSpacePath = getTempSpacePath(randomPath);
        String destFile = tempSpacePath + "/answer.zip";
        FileCompressUtil.compress(tempSpacePath + "/answers", destFile);
    }

    private void getUsersAnswer(String randomPath, List<Map<String, String>> answers) {
        String tempSpacePath = getTempSpacePath(randomPath);
        answers.forEach(answer -> {
            String url = answer.get("repo");
            String zipUrl = answer.get("zipUrl");
            String branch = answer.get("branch");
            String destPath = tempSpacePath + "/answers/" + answer.get("name");
            try {
                if (!isNull(url)) {
                    CloneRepertory.cloneRepo(url, branch, destPath);
                } else if (!isNull(zipUrl)) {
                    FileDownloadUtil.downloadZipFile(zipUrl, destPath);
                }
            } catch (GitAPIException e) {
                e.printStackTrace();
            }
        });
    }

    private boolean isNull(String url) {
        return Objects.equals("null", url);
    }

    private String getTempSpacePath(String randomPath) {
        File fileTemp = new File(".");
        String temp = fileTemp.getAbsolutePath() + "/backend/src/main/resources/static/simpleCodingQuiz/" + randomPath;
        File tempDir = new File(temp + "/answers");
        if (!tempDir.exists()) {
            tempDir.mkdirs();
        }
        return temp;
    }

    public List getSelectSimpleCodingQuizzes(String id) {
        List<Map> result = new ArrayList<>();
        Map simpleCodingQuiz = quizCenterFeign.getSimpleCodingQuizzesById(id).getBody();
        result.add(simpleCodingQuiz);

        return result;
    }

    public Map getAssignmentQuizzes(String type, Pageable pageable, Long quizId, Long createId, String description, List<String> tags, Long userId) throws BusinessException {
        switch (type) {
            case "BASIC_QUIZ":
                return quizCenterFeign.getBasicQuizzes(pageable.getPageNumber(), pageable.getPageSize(), quizId, createId, description, tags, userId).getBody();
            case "HOMEWORK_QUIZ":
                return quizCenterFeign.getHomeworkQuizzes(pageable.getPageNumber(), pageable.getPageSize(), quizId, createId, description, tags, userId).getBody();
            case "SIMPLE_CODING_QUIZ":
                return quizCenterFeign.getSimpleCodingQuizzes(pageable.getPageNumber(), pageable.getPageSize(), quizId, createId, description, tags, userId).getBody();
            case "ONLINE_CODING_QUIZ":
                return quizCenterFeign.getOnlineCodingQuizzes(pageable.getPageNumber(), pageable.getPageSize(), quizId, createId, description, tags, userId).getBody();
            case "ONLINE_LANGUAGE_QUIZ":
                return quizCenterFeign.getOnlineLanguageQuizzes(pageable.getPageNumber(), pageable.getPageSize(), quizId, createId, description, tags, userId).getBody();
            case ("SUBJECTIVE_QUIZ"):
                return quizCenterFeign.getSubjectiveQuizzes(pageable.getPageNumber(), pageable.getPageSize(), quizId, createId, description, tags, userId).getBody();
            case "LOGIC_QUIZ":
                return quizCenterFeign.getAllLogicQuizzes(pageable.getPageNumber(), pageable.getPageSize(), quizId, createId, description, tags, userId).getBody();
            case "SURVEY_QUIZ":
                return quizCenterFeign.getAllSurveyQuizzes(pageable.getPageNumber(), pageable.getPageSize(), quizId, createId, description, tags, userId).getBody();
        }
        throw new BusinessException("This quiz type was not found");
    }

    public List<AssignmentQuizData> copyQuizzesToOrganization(List<AssignmentQuizData> assignmentQuizData) {
        return quizCenterFeign.copyQuizzesToOrganization(assignmentQuizData);
    }

    public PageImpl getExcellentQuizzes(Long assignmentId, Long quizId, Pageable pageable) {
        Page<ReviewQuiz> reviewQuizzes = reviewQuizRepository.findAllByAssignmentIdAndQuizIdAndStatus(assignmentId, quizId, "优秀", pageable);

        String ids = reviewQuizzes.stream().map(reviewQuiz -> reviewQuiz.getStudentId().toString()).collect(Collectors.joining(","));

        List<Map> userInfos = userCenterService.getUsersByIds(ids);

        List excellentReviews = reviewQuizzes.stream().map(reviewQuiz -> {
            Map user = userInfos.stream()
                .filter(userInfo -> reviewQuiz.getStudentId().equals(Long.valueOf(userInfo.get("id").toString())))
                .findFirst().get();

            Map map = oMapper.convertValue(reviewQuiz, Map.class);
            map.put("userName", user.get("username"));
            return map;
        }).collect(Collectors.toList());
        return new PageImpl(excellentReviews, pageable, reviewQuizzes.getTotalElements());

    }

    public Boolean isLogicQuizFinish(Long studentId, Assignment assignment) {
        boolean isTimeOut = isTimeOut(studentId, assignment);
        if (isTimeOut) {
            saveLogicReview(studentId, assignment);
            return true;
        }
        return false;
    }

    private boolean isTimeOut(Long studentId, Assignment assignment) {
        Long quizId = assignment.getSelectedQuizzes().get(0).getQuizId();
        Map logicQuizSubmission = quizCenterFeign.getLogicQuizSubmission(assignment.getId(), quizId, studentId);
        if (Objects.isNull(logicQuizSubmission.get("submission"))) {
            return false;
        }
        Map logicQuiz = (Map) logicQuizSubmission.get("logicQuiz");
        Map submission = (Map) logicQuizSubmission.get("submission");
        try {
            Date startTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(
                submission.get("startTime")
                    .toString().split("\\.")[0]
                    .replace("T", " "));
            long timeBoxInMinutes = Long.valueOf(logicQuiz.get("timeBoxInMinutes").toString());
            return (new Date().getTime() - startTime.getTime()) >= timeBoxInMinutes;
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return false;
    }

    private void saveLogicReview(Long studentId, Assignment assignment) {
        ReviewQuiz reviewQuiz = new ReviewQuiz();
        reviewQuiz.setStudentId(studentId);
        reviewQuiz.setAssignmentId(assignment.getId());
        reviewQuiz.setTaskId(assignment.getTaskId());
        reviewQuiz.setCreateTime(new Date());
        reviewQuiz.setStatus("已完成");
        reviewQuiz.setGrade(-1);
        reviewQuizRepository.save(reviewQuiz);
    }

    public void submitQuizSuggestion(CreateQuizSuggestion createQuizSuggestion, User user) {
        createQuizSuggestion.updateSubmitter(user.getId());
        quizCenterFeign.submitQuizSuggestion(createQuizSuggestion);
    }

    public List getSelectingSurveyQuizzes(String ids) {
        return quizCenterFeign.getSurveyQuizzes(ids).getBody();
    }

    public Map<Long, List<Map>> calculateBasicQuizAssignmentScore(Long assignmentId) {
        return quizCenterFeign.calculateBasicQuizAssignmentScore(assignmentId);
    }
}
